package com.dualsportmaps.android;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;

import net.osmand.plus.OsmandSettings;
import android.app.Activity;
import android.content.Context;
import android.location.Location;
import android.widget.Toast;

import com.dualsportmaps.android.DsmResourceManager.DsmMapDownloadRequest;
import com.dualsportmaps.android.DsmResourceManager.DsmResourceManagerException;
import com.dualsportmaps.android.model.DsmMap;
import com.dualsportmaps.android.model.DsmMapPhoto;
import com.dualsportmaps.android.model.DsmMapTrack;
import com.dualsportmaps.android.model.DsmMapWpt;
import com.dualsportmaps.android.shared.model.DsmMapPhotoProxy;
import com.dualsportmaps.android.shared.model.DsmMapProxy;
import com.dualsportmaps.android.shared.model.DsmMapTrackProxy;
import com.dualsportmaps.android.shared.model.DsmMapWptProxy;
import com.google.web.bindery.requestfactory.shared.Receiver;
import com.google.web.bindery.requestfactory.shared.ServerFailure;

public class DsmMapDownload {
//	private SoftReference<ArrayList<EncodedPolyline>> eps = new SoftReference<ArrayList<EncodedPolyline>>(null);
	ArrayList<EncodedPolyline> eps = null;
	private SoftReference<ArrayList<DsmMapTrack>> tracks = new SoftReference<ArrayList<DsmMapTrack>>(null);
	private SoftReference<ArrayList<DsmMapWpt>> waypoints = new SoftReference<ArrayList<DsmMapWpt>>(null);
	private SoftReference<ArrayList<DsmMapPhoto>> photos = new SoftReference<ArrayList<DsmMapPhoto>>(null);
	DsmMap map;
	private static List<IDsmMapDownloadListener> listeners = new ArrayList<IDsmMapDownloadListener>();
	
	public DsmMapDownload(String dsmMapId, boolean update, Activity activity, boolean pushToService) throws DsmMapDownloadException {
		this(loadMap(dsmMapId, DsmResourceManager.getApplicationContext()));
		if(map != null) {
			boolean loadSavedMap = true;
			try {
				if(DsmResourceManager.getDsmResourceManager().mapAlreadyLoaded(this))
					loadSavedMap = false;
			} catch (DsmResourceManagerException e) {
			}
			if(loadSavedMap)
				dsmMapDownloaded(this);
		}
		if(update && OsmandSettings.isInternetConnectionAvailable(DsmResourceManager.getApplicationContext()) && OsmandSettings.isUsingInternetToDownloadTiles(OsmandSettings.getPrefs(DsmResourceManager.getApplicationContext()))) {
			DsmMapDownload.downloadMapFromWeb(dsmMapId,this,activity,pushToService);			
		}
	}
	public DsmMapDownload(String dsmMapId) throws DsmMapDownloadException {
		this(dsmMapId,false,null,false);
	}
	private DsmMapDownload(SavedDsmMap tdm) throws DsmMapDownloadException {
		if(tdm == null)
			throw new DsmMapDownloadException();
		this.setMap(tdm.map);
		this.setWaypoints(tdm.waypoints);
		this.setPhotos(tdm.photos);
		this.setTracks(tdm.tracks);
	}
	@Override
	public String toString() {
		try {
			if(map.getDsmMapName() == null)
				map.setDsmMapName("Latest Search Results");
			return map.getDsmMapName();
		} catch (Exception e) {
			return "Latest Search Results";
		}
	}
	public ArrayList<EncodedPolyline> getEncodedPolylines() {
		if(eps == null) { //.get()
			ArrayList<EncodedPolyline> neweps = new ArrayList<EncodedPolyline>();
			for(DsmMapTrack track : getTracks()) {
				neweps.add(new EncodedPolyline(track.getDsmMapTrackGooglePoints(),track.getDsmMapTrackGoogleLevels(), track.getDsmMapTrackColor()));
			}
//			eps = new SoftReference<ArrayList<EncodedPolyline>>(neweps); 
			eps = neweps;
			if(map.getDsmMapBound1Lat() == 0) {
				float minlat = 999999999;
				float minlng = 999999999;
				float maxlat = -999999999;
				float maxlng = -999999999;
				for(EncodedPolyline ep : neweps) {
					minlat = Math.min(minlat, ep.bound1Lat);
					minlng = Math.min(minlng, ep.bound1Lng);
					maxlat = Math.max(maxlat, ep.bound2Lat);
					maxlng = Math.max(maxlng, ep.bound2Lng);
				}
				map.setDsmMapBound1Lat(Float.valueOf(minlat).doubleValue());
				map.setDsmMapBound1Lng(Float.valueOf(minlng).doubleValue());
				map.setDsmMapBound2Lat(Float.valueOf(maxlat).doubleValue());
				map.setDsmMapBound2Lng(Float.valueOf(maxlng).doubleValue());
			}
		}
//		return eps.get();
		return eps;
	}
	public void clearEncodedPolylines() {
		if(eps != null) {
			eps.clear();		
			eps = null;
		}
	}
	public ArrayList<DsmMapTrack> getTracks() {
		if(tracks.get() == null) {
			ArrayList<DsmMapTrack> retval = loadDsmMapTracks(this.map.getDsmMapId(),DsmResourceManager.getApplicationContext());
			tracks = new SoftReference<ArrayList<DsmMapTrack>>(retval);
			return retval;
		}
		return tracks.get();
	}

	public void setTracks(ArrayList<DsmMapTrack> tracks) {
		this.tracks.clear();
		this.tracks = new SoftReference<ArrayList<DsmMapTrack>>(tracks);
	}

	public ArrayList<DsmMapWpt> getWaypoints() {
		if(waypoints.get() == null) {
			ArrayList<DsmMapWpt> retval = loadDsmMapWpts(this.map.getDsmMapId(),DsmResourceManager.getApplicationContext());
			waypoints = new SoftReference<ArrayList<DsmMapWpt>>(retval);
			return retval;
		}
		return waypoints.get();
	}

	public void setWaypoints(ArrayList<DsmMapWpt> waypoints) {
		this.waypoints.clear();
		this.waypoints = new SoftReference<ArrayList<DsmMapWpt>>(waypoints);
	}

	public ArrayList<DsmMapPhoto> getPhotos() {
		if(photos.get() == null) {
			ArrayList<DsmMapPhoto> retval = loadDsmMapPhotos(this.map.getDsmMapId(),DsmResourceManager.getApplicationContext());
			photos = new SoftReference<ArrayList<DsmMapPhoto>>(retval);
			return retval;
		}
		return photos.get();
	}

	public void setPhotos(ArrayList<DsmMapPhoto> photos) {
		this.photos.clear();
		this.photos = new SoftReference<ArrayList<DsmMapPhoto>>(photos);
	}

	public DsmMap getMap() {
		return map;
	}

	public void setMap(DsmMap map) {
		this.map = map;
	}
	public Location getMapCenter() throws Exception {
		Location l = new Location("center");
		l.setLatitude((map.getDsmMapBound2Lat()-map.getDsmMapBound1Lat())/2+map.getDsmMapBound1Lat());
		l.setLongitude((map.getDsmMapBound2Lng()-map.getDsmMapBound1Lng())/2+map.getDsmMapBound1Lng());
		return l;
	}
	private static boolean waitingForDownload = false;
	private static synchronized void downloadMapFromWeb(final String dsmMapId, final DsmMapDownload dsmMap, final Activity mapActivity, boolean pushToService) {
		final SavedDsmMap newMap = new SavedDsmMap();
		if(pushToService) {
			try {
				DsmResourceManager.getDsmResourceManager().asyncLoader.requestToLoadDsmMap(new DsmMapDownloadRequest(dsmMapId));
			} catch (DsmResourceManagerException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
//			DsmResourceManager.getDsmMapDownloadServiceManager().downloadMapFromWeb(dsmMapId);
		} else {
			waitingForDownload = true;
			DsmConnectionFactory.getDsmConnection().getDsmMap(dsmMapId).fire(new Receiver<DsmMapProxy>() {
				@Override
				public void onSuccess(DsmMapProxy response) {
					downloadMapDetailsFromWeb(newMap,response, dsmMap, mapActivity);
				}
		
				@Override
				public void onFailure(ServerFailure error) {
					Toast.makeText(mapActivity, "Download map failure.", Toast.LENGTH_LONG).show();
				}
			});
		}
		while(waitingForDownload) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void setWaitingForDownload(boolean wfd) {
		waitingForDownload = wfd;
	}
	public static synchronized void mapDownloadedFromWeb(final String dsmMapId) {
		try {
			DsmMapDownload newmap = new DsmMapDownload(dsmMapId);
			dsmMapDownloaded(newmap);
		} catch (DsmMapDownloadException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}	
	}
	private static synchronized void downloadMapDetailsFromWeb(final SavedDsmMap newMap,DsmMapProxy response,final DsmMapDownload dsmMap, final Activity mapActivity) {
		boolean mapUpdated = true;
		if(dsmMap.map != null) 
			if(response.getDsmMapTS().equals(dsmMap.map.getDsmMapTS())) {
				mapUpdated = false;
			}
		if(mapUpdated) {
			final String dsmMapId = response.getDsmMapId();
			newMap.map = new DsmMap(
				response.getDsmMapId(), response.getDsmMapName(), response.getDsmMapDesc(),
				response.getDsmMapBound1Lat(), response.getDsmMapBound1Lng(),
				response.getDsmMapBound2Lat(), response.getDsmMapBound2Lng(), response.getDsmMapTS(),
				response.getDsmAppUserId(), response.isDsmMapViewable(), response.getDsmMapMaxZoom(),
				response.getDsmAppUserHandle());
			response = null;
			if(mapActivity != null) {
				mapActivity.runOnUiThread(new Runnable() {
					@Override
					public void run() {
						try {
							if(newMap.map.getDsmMapName().equals("All tracks in search area.")) {
								newMap.map.setDsmMapName(null);
							}
						} catch (Exception e) {
						}
						if(newMap.map.getDsmMapName() == null) {
							newMap.map.setDsmMapId("SEARCHRESULTS");
							Toast.makeText(mapActivity,"Loading search results.",Toast.LENGTH_LONG).show();
						}
						else if(newMap.map.getDsmMapId() != null) {
							Toast.makeText(mapActivity,newMap.map.getDsmMapName() + " is updating.",Toast.LENGTH_LONG).show();
						}  
					}
				});
			}
			try {
				DsmConnectionFactory.getDsmConnection().getDsmMapTracks(dsmMapId).fire(new Receiver<List<DsmMapTrackProxy>>() {
					@Override
					public void onFailure(ServerFailure error) {
						if(mapActivity != null) {
							mapActivity.runOnUiThread(new Runnable() {
								@Override
								public void run() {
									Toast.makeText(mapActivity, "Track download failure.", Toast.LENGTH_LONG).show();
								}
							});
						}
					}
					@Override
					public void onSuccess(List<DsmMapTrackProxy> response) {
						for(DsmMapTrackProxy track : response) {
							DsmMapTrack tdsmtrack = new DsmMapTrack(track.getDsmMapTrackGooglePoints(),track.getDsmMapTrackGoogleLevels(), track.getDsmMapTrackColor(), track.getDsmMapTrackBound1Lat(),track.getDsmMapTrackBound1Lng(),track.getDsmMapTrackBound2Lat(),track.getDsmMapTrackBound2Lng());
							tdsmtrack.setDsmMapTrackName(track.getDsmMapTrackName());
							tdsmtrack.setDsmMapTrackDistance(track.getDsmMapTrackDistance());
							tdsmtrack.setDsmMapTrackDifficulty(track.getDsmMapTrackDifficulty());
							newMap.tracks.add(tdsmtrack);
							track = null;
						}
						response = null;
						DsmConnectionFactory.getDsmConnection().getDsmMapWpts(dsmMapId).fire(new Receiver<List<DsmMapWptProxy>>() {
							@Override
							public void onFailure(ServerFailure error) {
								if(mapActivity != null) {
									mapActivity.runOnUiThread(new Runnable() {
										@Override
										public void run() {
											Toast.makeText(mapActivity, "Waypoint download failure.", Toast.LENGTH_LONG).show();
										}
									});
								}
							}
							@Override
							public void onSuccess(List<DsmMapWptProxy> response) {
								newMap.waypoints = new ArrayList<DsmMapWpt>();
								for(DsmMapWptProxy wpt : response) {
									newMap.waypoints.add(new DsmMapWpt(
											wpt.getDsmMapWptId(),
											wpt.getDsmMapId(),
											wpt.getDsmMapWptName(),
											wpt.getDsmMapWptDesc(),
											wpt.getDsmMapWptLat(),
											wpt.getDsmMapWptLng(),
											wpt.getDsmMapWptIcon()
											));
									wpt = null;
								}
								response = null;
								DsmConnectionFactory.getDsmConnection().getDsmMapPhotos(dsmMapId).fire(new Receiver<List<DsmMapPhotoProxy>>() {
									@Override
									public void onFailure(ServerFailure error) {
										if(mapActivity != null) {
											mapActivity.runOnUiThread(new Runnable() {
												@Override
												public void run() {
													Toast.makeText(mapActivity, "Photo download failure.", Toast.LENGTH_LONG).show();
												}
											});
										}
									}
									@Override
									public void onSuccess(List<DsmMapPhotoProxy> response) {
										newMap.photos = new ArrayList<DsmMapPhoto>();
										for(DsmMapPhotoProxy photo : response) {
											DsmMapPhoto tphoto = new DsmMapPhoto();
											tphoto.setDsmAppUserHandle(photo.getDsmAppUserHandle());
											tphoto.setDsmAppUserId(photo.getDsmAppUserId());
											tphoto.setDsmMapId(photo.getDsmMapId());
											tphoto.setDsmMapPhotoCamera(photo.getDsmMapPhotoCamera());
											tphoto.setDsmMapPhotoCaption(photo.getDsmMapPhotoCaption());
											tphoto.setDsmMapPhotoId(photo.getDsmMapPhotoId());
											tphoto.setDsmMapPhotoLat(photo.getDsmMapPhotoLat());
											tphoto.setDsmMapPhotoLng(photo.getDsmMapPhotoLng());
											tphoto.setDsmMapPhotoTS(photo.getDsmMapPhotoTS());
											newMap.photos.add(tphoto);
											photo = null;
										}
										response = null;
										mapActivity.runOnUiThread(new Runnable() {
											@Override
											public void run() {
												saveDsmMap(newMap,mapActivity.getApplicationContext());
												try {
													dsmMapDownloaded(new DsmMapDownload(newMap));
												} catch (DsmMapDownloadException e) {
													e.printStackTrace();
												}
												waitingForDownload = false;
											}
										});
									}
								});	
							}
						});
					}
				});
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.getCause().printStackTrace();
				e.printStackTrace();
				waitingForDownload = false;
			}
		} else {
			response = null;
			waitingForDownload = false;
		}
	}
	public static void saveDsmMap(DsmMapDownload dsmMap, Context context) {
		saveDsmMap(new SavedDsmMap(dsmMap),context);
	}
	public static void saveDsmMap(SavedDsmMap dsmMap, Context context) {
		try {
			FileOutputStream fos = context.openFileOutput("dsmMap-"+dsmMap.map.getDsmMapId(), Context.MODE_PRIVATE);
			ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(fos));
			oos.writeObject(dsmMap.map); 
			oos.close();
			if(dsmMap.tracks.size() > 0) {
				fos = context.openFileOutput("dsmMapTracks-"+dsmMap.map.getDsmMapId(), Context.MODE_PRIVATE);
				oos = new ObjectOutputStream(new BufferedOutputStream(fos));
				oos.writeObject(dsmMap.tracks); 
				oos.close();
			}
			if(dsmMap.waypoints.size() > 0) {
				fos = context.openFileOutput("dsmMapWpts-"+dsmMap.map.getDsmMapId(), Context.MODE_PRIVATE);
				oos = new ObjectOutputStream(new BufferedOutputStream(fos));
				oos.writeObject(dsmMap.waypoints); 
				oos.close();
			}
			if(dsmMap.photos.size() > 0) {
				fos = context.openFileOutput("dsmMapPhotos-"+dsmMap.map.getDsmMapId(), Context.MODE_PRIVATE);
				oos = new ObjectOutputStream(new BufferedOutputStream(fos));
				oos.writeObject(dsmMap.photos); 
				oos.close();
			}
		} catch (Exception e) {
		}
	}
	public static SavedDsmMap loadMap(String dsmMapId,Context context) {
		try {
			SavedDsmMap dsmMap = new SavedDsmMap();
			dsmMap.map = loadDsmMap(dsmMapId,context);
			dsmMap.tracks = loadDsmMapTracks(dsmMapId,context);
			dsmMap.waypoints = loadDsmMapWpts(dsmMapId,context);
			dsmMap.photos = loadDsmMapPhotos(dsmMapId,context);
			return dsmMap;
		} catch (Exception e) {
			return null;
		} 
	}
	public static DsmMap loadDsmMap(String dsmMapId,Context context) {
		try {
			FileInputStream fis = context.openFileInput("dsmMap-"+dsmMapId);
			ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
			DsmMap dsmMap = (DsmMap) ois.readObject();
			if(dsmMap.getDsmMapId() == null) {
				deleteSavedMap(dsmMapId,context);
			} else if(!dsmMapId.equals(dsmMap.getDsmMapId())) {
				deleteSavedMap(dsmMapId,context);
			}
			return dsmMap;
		} catch (Exception e) {
			deleteSavedMap(dsmMapId,context);
			return null;
		} 
	}
	@SuppressWarnings("unchecked")
	public static ArrayList<DsmMapTrack> loadDsmMapTracks(String dsmMapId,Context context) {
		try {
			FileInputStream fis = context.openFileInput("dsmMapTracks-"+dsmMapId);
			ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
			ArrayList<DsmMapTrack> tracks = (ArrayList<DsmMapTrack>) ois.readObject();
			return tracks;
		} catch (Exception e) {
			return new ArrayList<DsmMapTrack>();
		} 
	}
	@SuppressWarnings("unchecked")
	public static ArrayList<DsmMapWpt> loadDsmMapWpts(String dsmMapId,Context context) {
		try {
			FileInputStream fis = context.openFileInput("dsmMapWpts-"+dsmMapId);
			ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
			ArrayList<DsmMapWpt> wpts = (ArrayList<DsmMapWpt>) ois.readObject();
			return wpts;
		} catch (Exception e) {
			return new ArrayList<DsmMapWpt>();
		} 
	}
	@SuppressWarnings("unchecked")
	public static ArrayList<DsmMapPhoto> loadDsmMapPhotos(String dsmMapId,Context context) {
		try {
			FileInputStream fis = context.openFileInput("dsmMapPhotos-"+dsmMapId);
			ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(fis));
			ArrayList<DsmMapPhoto> photos = (ArrayList<DsmMapPhoto>) ois.readObject();
			return photos;
		} catch (Exception e) {
			return new ArrayList<DsmMapPhoto>();
		} 
	}
	public static void deleteSavedMap(String dsmMapId,Context context) {
		context.deleteFile("dsmMap-"+dsmMapId);
		context.deleteFile("dsmMapTracks-"+dsmMapId);
		context.deleteFile("dsmMapWpts-"+dsmMapId);
		context.deleteFile("dsmMapPhotos-"+dsmMapId);
	}
	private static void dsmMapDownloaded(DsmMapDownload dsmMapDownload) {
		for(IDsmMapDownloadListener listener : new Vector<IDsmMapDownloadListener>(listeners)) {
//			System.out.println(listener.getClass().getName());
			listener.addDsmMap(dsmMapDownload);
		}
	}
	public static void removeDsmMapDownload(String dsmMapId) {
		for(IDsmMapDownloadListener listener : new Vector<IDsmMapDownloadListener>(listeners)) {
			listener.removeDsmMap(dsmMapId);
		}
	}
	public static List<IDsmMapDownloadListener> getDownloadListners() {
		return new Vector<IDsmMapDownloadListener>(listeners);
	}
	public static void addListener(IDsmMapDownloadListener listener) {
		if(!listeners.contains(listener))
			listeners.add(listener);
	}
	public static void removeListener(IDsmMapDownloadListener listener) {
			listeners.remove(listener);
	}
	static class SavedDsmMap implements Serializable {
		private static final long serialVersionUID = 1L;
		ArrayList<DsmMapTrack> tracks = new ArrayList<DsmMapTrack>();
		ArrayList<DsmMapWpt> waypoints = new ArrayList<DsmMapWpt>();
		ArrayList<DsmMapPhoto> photos = new ArrayList<DsmMapPhoto>();
		DsmMap map;
		
		public SavedDsmMap(DsmMapDownload dsmMap) {
			this.map = dsmMap.map;
			this.waypoints = dsmMap.getWaypoints();
			this.photos = dsmMap.getPhotos();
			this.tracks = dsmMap.getTracks();
		}

		public SavedDsmMap() {
			// TODO Auto-generated constructor stub
		}
		
	}
	public static boolean isSomethingBeingDownloaded() {
		return waitingForDownload;
	}
	public class DsmMapDownloadException extends Exception {
		private static final long serialVersionUID = 1L;
		
	}
	
}
